﻿using System;
using System.Linq;
using System.ComponentModel.Composition;
using Microsoft.TeamFoundation;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.Framework.Server;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Server;

namespace TfsRollUp.Core
{
    internal class Plugin : ISubscriber
    {
        public string Name
        {
            get { return Constants.TfsRollUpPlugin; }
        }

        public SubscriberPriority Priority
        {
            get { return SubscriberPriority.Low; }
        }

        public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext, NotificationType notificationType, object notificationEventArgs, out int statusCode, out string statusMessage, out ExceptionPropertyCollection properties)
        {
            TeamFoundationTrace.MethodEntry();
            statusCode = 0;
            properties = null;
            statusMessage = String.Empty;
            if (notificationType == NotificationType.Notification && notificationEventArgs is WorkItemMetadataChangedNotification)
            {
                var rollUpService = requestContext.GetService<Service>();
                rollUpService.ServiceFlushCache();
            }

            if (notificationType == NotificationType.Notification && notificationEventArgs is WorkItemChangedEvent)
            {
                WorkItemChangedEvent ev = notificationEventArgs as WorkItemChangedEvent;
                int workItemId = 0;
                try
                {
                    // Get the id of the work item that was just changed by the user.
                    workItemId = ev.CoreFields.IntegerFields[0].NewValue;
                    TeamFoundationTrace.Verbose("{0}.ProcessEvent: Processing workitemid:{1}", Constants.TfsRollUpPlugin, workItemId);
                    ProcessWorkItem(requestContext, ev, workItemId);
                }
                catch (Exception ex)
                {
                    TeamFoundationApplication.LogException(string.Format("{0}.ProcessEvent: Error processing workitem:{1}", Constants.TfsRollUpPlugin, workItemId), ex);
                }
                
            }
            TeamFoundationTrace.MethodExit();
            return EventNotificationStatus.ActionPermitted;
        }

        private void ProcessWorkItem(TeamFoundationRequestContext requestContext, WorkItemChangedEvent ev, int workItemId)
        {
            TeamFoundationTrace.MethodEntry(new object[] {workItemId});

            var rollUpService = requestContext.GetService<Service>();
            if (rollUpService == null)
            {
                TeamFoundationTrace.Error("{0}.ProcessWorkItem: rollUpService is null", Constants.TfsRollUpPlugin);
                return;
            }
            if (!rollUpService.Settings.EnableRollUpProcesing)
            {
                TeamFoundationTrace.Verbose("{0}.ProcessWorkItem: Processing has been disabled", Constants.TfsRollUpPlugin);
                return;
            }
            var store = rollUpService.Store(requestContext);
            if (store == null)
            {
                TeamFoundationTrace.Error("{0}.ProcessWorkItem: store is null", Constants.TfsRollUpPlugin);
                return;
            }
            var workItem = store.GetWorkItem(workItemId);
            TeamFoundationTrace.Verbose("{0}.ProcessWorkItem: WorkItemId:{1} read", Constants.TfsRollUpPlugin, workItemId);
            if (ShouldProcessWorkItem(workItem, ev, rollUpService, store))
            {
                var fieldList = rollUpService.RollUpFields(requestContext).GetRollUpFieldList((string)workItem.Fields[CoreField.TeamProject].Value, workItem.Type.Name);

                // Push this processing request onto the WorkItemRootQueue
                rollUpService.WorkItemRootQueue.Add(workItem.Root(fieldList)[0]);
            }
            TeamFoundationTrace.MethodExit();
        }

        /// <summary>
        /// Filters the workitem; looks for changes and if relevant for rollup fields
        /// </summary>
        /// <param name="workItem"></param>
        /// <param name="ev"></param>
        /// <param name="rollUpService"></param>
        /// <param name="store"></param>
        /// <returns></returns>
        private bool ShouldProcessWorkItem(WorkItem workItem, WorkItemChangedEvent ev, Service rollUpService, WorkItemStore store)
        {
            TeamFoundationTrace.MethodEntry();
            #region ValidateParameters
            if (workItem == null )
            {
                TeamFoundationTrace.Verbose("ShouldProcessWorkItem: workItem is null");
                TeamFoundationTrace.MethodExit();
                return false;
            }
            if (ev == null)
            {
                TeamFoundationTrace.Verbose("ShouldProcessWorkItem: ev is null");
                TeamFoundationTrace.MethodExit();
                return false;
            }
            if (rollUpService == null)
            {
                TeamFoundationTrace.Verbose("ShouldProcessWorkItem: rollUpService is null");
                TeamFoundationTrace.MethodExit();
                return false;
            }
            if (store == null)
            {
                TeamFoundationTrace.Verbose("ShouldProcessWorkItem: store is null");
                TeamFoundationTrace.MethodExit();
                return false;
            }
            #endregion

            if (workItem.RelatedLinkCount == 0)
            {
                TeamFoundationTrace.Verbose("{0}.ShouldProcessWorkItem: No links found on WorkItemId:{1}", Constants.TfsRollUpPlugin, workItem.Id);
                return false;
            }

            if( ev.ChangerSid == rollUpService.SystemAccountSid )
            {
                TeamFoundationTrace.Verbose("{0}.ShouldProcessWorkItem: ChangerSid '{1}' is TfsServiceAccount, we advise against circular updates", Constants.TfsRollUpPlugin, workItem.ChangedBy);
                return false;
            }

            var fieldList = rollUpService.RollUpFields(store.TeamProjectCollection.Uri).GetRollUpFieldList((string)workItem.Fields[CoreField.TeamProject].Value, workItem.Type.Name);
            if (fieldList == null || fieldList.Length == 0)
            {
                TeamFoundationTrace.Verbose("{0}.ShouldProcessWorkItem: RollUp fields for WorkItemId:{1} was not found in TfsCollection:'{2}' TeamProject:'{3}' WorkItemType:'{4}'", Constants.TfsRollUpPlugin, workItem.Id, store.TeamProjectCollection.Name, (string)workItem.Fields[CoreField.TeamProject].Value, workItem.Type.Name);
                TeamFoundationTrace.MethodExit();
                return false;
            }

            if (ev.ChangedFields != null && ev.ChangedFields.StringFields != null)
            {
                // Look for changed fields that match a rollup fieldname
                foreach (var changedField in ev.ChangedFields.StringFields)
                {
                    if (changedField.NewValue != changedField.OldValue)
                    {
                        var matchFound = (from p in fieldList where p.Equals(changedField.ReferenceName) select p).SingleOrDefault();
                        if (!string.IsNullOrWhiteSpace(matchFound))
                        {
                            TeamFoundationTrace.Verbose("{0}.ShouldProcessWorkItem: Change found. Field:{1} OldValue:{2} NewValue:{3}", Constants.TfsRollUpPlugin, matchFound, changedField.OldValue, changedField.NewValue);
                            TeamFoundationTrace.MethodExit();
                            return true;
                        }
                    }
                }
            }

            if (ev.AddedRelations != null && ev.AddedRelations.Length > 0)
            {
                TeamFoundationTrace.Verbose("{0}.ShouldProcessWorkItem: Relations was added for WorkItemId:{1}", Constants.TfsRollUpPlugin, workItem.Id);
                TeamFoundationTrace.MethodExit();
                return true;
            }
            TeamFoundationTrace.MethodExit();
            return false;
        }

        public Type[] SubscribedTypes()
        {
            return new Type[] { typeof(WorkItemChangedEvent), typeof(WorkItemMetadataChangedNotification) };
        }
    }
}
